/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.mrroman.linksender.filesender.server;

import com.mrroman.linksender.filesender.sendables.Sendable;
import com.mrroman.linksender.filesender.sendables.SendableError;
import com.mrroman.linksender.filesender.authenticator.HttpAuthenticator;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Arrays;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author gorladam
 */
public class Server implements Runnable {

    protected ServerSocket server;
    protected int port;
    protected ThreadPoolExecutor threadPool;
    protected int poolSize;
    protected Sendable sendableRoot;
    protected String homePage, serverName, outputDirectory, menu[];
    private HttpAuthenticator authenticator;
    private Logger logger = Logger.getLogger(Server.class.getName());
    private boolean started;

    public Server() {
        started = true;
    }

    public Server(int port, int poolSize, String homePage, String serverName) {
        this();
        setParameters(port, poolSize, homePage, serverName);
    }

    public void setParameters(int port, int poolSize, String homePage, String serverName) {
        this.port = port;
        this.poolSize = poolSize;
        this.homePage = homePage;
        this.serverName = serverName;
        logger.info("Set server parameters: "+"port="+port+", pool="+poolSize+", homepage="+homePage+", name="+serverName);
        if (poolSize < 1 || poolSize > 300) {
            threadPool = new ThreadPoolExecutor(0, 300, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
        } else if (poolSize == 1) {
            threadPool = new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
        } else if (poolSize > 1) {
            threadPool = new ThreadPoolExecutor(poolSize, poolSize, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
        }
    }

    public Sendable getSendableRoot() {
        return sendableRoot;
    }

    public void setSendableRoot(Sendable sendableRoot) {
        this.sendableRoot = sendableRoot;
        logger.info("Set server root: "+sendableRoot);
    }

    public Sendable getSendableRoot(Socket socket) {
        return getSendableRoot();
    }

    public int getPort() {
        return port;
    }

    public boolean isStarted() {
        return started;
    }

    public void setStarted(boolean started) {
        this.started = started;
        if (!started && server != null) {
            try {
                server.close();
                server = null;
            } catch (IOException ex) {
            }
        }
    }

//    public HttpAuthenticator getAuthenticator() {
//        return authenticator;
//    }
//
//    public void setAuthenticator(HttpAuthenticator authenticator) {
//        this.authenticator = authenticator;
//        logger.info("Set server authenticator: "+authenticator);
//    }

    public String getOutputDirectory() {
        return outputDirectory;
    }

    public void setOutputDirectory(String outputDirectory) {
        this.outputDirectory = outputDirectory;
        if (outputDirectory != null) {
            String separator = System.getProperty("file.separator");
            if (!this.outputDirectory.endsWith(separator)) {
                this.outputDirectory = this.outputDirectory + separator;
            }
        }
        logger.info("Set server output directory: "+this.outputDirectory);
    }

    public void setMenu(String... menu) {
        this.menu = menu;
        logger.info("Set server menu: "+Arrays.asList(menu));
    }

    protected void protectedRun() throws IOException {
        try {
            server = new ServerSocket(port);
            ServerThreadFactory serverThreadFactory = new ServerThreadFactory(serverName, homePage, outputDirectory);
            serverThreadFactory.setMenu(menu);
            SendableError s_503 = new SendableError(null, "error", 503, new String[][]{
                        {HttpTools.SERVER, serverThreadFactory.getServerName()},
                        {HttpTools.CONTENTTYPE, HttpTools.CONTENTTYPE_TEXT_HTML},
                        {HttpTools.RETRYAFTER, "10"},});
            while (started) {
                Socket client = server.accept();
                //pool.execute(new ServerThread(server.accept(), sendableRoot));
                Runnable t = null;
                if (threadPool.getActiveCount() == poolSize - 1) {
                    t = serverThreadFactory.getServerRunnable(client, s_503);
                    logger.warning("max requests reached (" + poolSize + "), sending 503");
                } else {
                    t = serverThreadFactory.getServerRunnable(client, getSendableRoot(client));
                }
                threadPool.execute(t);
            }
        } finally {
            if (server != null) {
                server.close();
            }
        }
    }

    @Override
    public void run() {
        try {
            protectedRun();
        } catch (SocketException ex) {
            if (started) {
                logger.log(Level.SEVERE, null, ex);
            }
        } catch (IOException ex) {
            logger.log(Level.SEVERE, null, ex);
        }
    }
}
